/*
 *    Copyright © OpenAtom Foundation.
 *
 *    Licensed under the Apache License, Version 2.0 (the "License");
 *    you may not use this file except in compliance with the License.
 *    You may obtain a copy of the License at
 *
 *         http://www.apache.org/licenses/LICENSE-2.0
 *
 *    Unless required by applicable law or agreed to in writing, software
 *    distributed under the License is distributed on an "AS IS" BASIS,
 *    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 *    See the License for the specific language governing permissions and
 *    limitations under the License.
 */

package com.inspur.edp.bef.core.session;

import com.inspur.edp.bef.api.action.assembler.IDefaultValueProcessor;
import com.inspur.edp.bef.api.be.IBEManager;
import com.inspur.edp.bef.api.lcp.BefContext;
import com.inspur.edp.bef.api.repository.RepositoryConst;
import com.inspur.edp.bef.core.lock.LockService;
import com.inspur.edp.bef.core.scope.BefScopeManager;
import com.inspur.edp.bef.core.tcc.BefTccParam;
import com.inspur.edp.bef.core.session.transaction.BufferTransactionManager;
import com.inspur.edp.cef.api.session.ICefSession;
import com.inspur.edp.cef.api.session.ICefSessionItem;
import com.inspur.edp.cef.core.scope.CefScopeNode;
import com.inspur.edp.commonmodel.core.session.distributed.DistributedSessionPart;
import com.inspur.edp.commonmodel.core.session.distributed.RootEditToken;
import com.inspur.edp.commonmodel.core.session.distributed.RootEditTokenImpl;
import com.inspur.edp.commonmodel.core.session.distributed.SessionEditToken;
import java.lang.ref.SoftReference;
import java.time.Duration;
import java.time.LocalDateTime;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.Iterator;
import java.util.LinkedHashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.Set;
import java.util.Stack;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.locks.ReadWriteLock;
import java.util.concurrent.locks.ReentrantReadWriteLock;
import java.util.stream.Collectors;
import lombok.Getter;
import lombok.Setter;

/**
 * 功能Session
 *
 * <p>一个功能session实例表示一次功能会话，它的生命周期从打开功能到关闭功能为止，期间访问的数据缓存以及相关状态和控制规则与其他功能完全隔离。
 * 一次会话的多次请求都能访问到之前处理过的数据。
 */
public class FuncSession implements ICefSession {

  // region 字段
  public ConcurrentHashMap<String, IBEManager> dicBeManager;
  BefContext befContext;
  private LockService locker;
//  private final SessionConfig distributedConfig;
  @Getter
  private final boolean distributed;

  @Getter
  @Setter
  private Map<String,Object> sessionTrace;

  // endregion

  // region Constructor
  public FuncSession(Duration persistenceDuration) {
    this(persistenceDuration, false);
  }

  public FuncSession(Duration persistenceDuration, boolean distributed) {
    this.persistenceDuration = persistenceDuration;
    lastOn = persistenceDuration != null ? LocalDateTime.now() : null;

    dicBeManager = new ConcurrentHashMap<>();
    sessionItemsStack.push(new SessionItemMap());
//    this.distributedConfig = SpringBeanUtils.getBean(SessionConfigService.class).getSessionConfig();
    this.distributed = distributed;
    updateLast();
  }

  @Deprecated
  public FuncSession() {
    this(null);
  }

  // endregion

  // region 属性
//  private FuncSessionDac dac;
//  private FuncSessionDac getDac() {
//    if(dac == null){
//      dac = FuncSessionDacFactory.getDac(distributedConfig);
//    }
//    return dac;
//  }
  /**
   * 功能SessionId
   *
   * <p>每一个功能Session实例在整个系统中都有一个独立的Id,与其他功能Session区分
   */
  private String privateSessionId;

  public final String getSessionId() {
    return privateSessionId;
  }

  public final void setSessionId(String value) {
    privateSessionId = value;
  }

  private BufferTransactionManager privateTransactionManager;

  public final BufferTransactionManager getBETransactionManager() {
    return privateTransactionManager;
  }

  public final void setBETransactionManager(BufferTransactionManager value) {
    privateTransactionManager = value;
  }

  private ReadWriteLock sessionLock = new ReentrantReadWriteLock();

  public ReadWriteLock getSessionLock() {return sessionLock;}

  private boolean passAddLock = false;

  public boolean getPassAddLock() {
    return passAddLock;
  }

  public void setPassAddLock(boolean value) {
    passAddLock = value;
  }

  public final LockService getLockService() {
    if (locker == null) {
      locker = new LockService(this);
    }
    return locker;
  }


  private BefScopeManager privateScopeManager;

  public final BefScopeManager getScopeManager() {
    return privateScopeManager;
  }

  public final void setScopeManager(BefScopeManager value) {
    privateScopeManager = value;
  }

  private IDefaultValueProcessor privateDefaultValueProcessor;

  public final IDefaultValueProcessor getDefaultValueProcessor() {
    return privateDefaultValueProcessor;
  }

  public final void setDefaultValueProcessor(IDefaultValueProcessor value) {
    privateDefaultValueProcessor = value;
  }

  public final BefContext getBefContext() {
    if (befContext == null) {
      befContext = new BefContext();
      // 单元测试需要注释
      //TODO java版临时注释 年度表先写死
      String yearInfo = "2019";
      //String yearInfo = String.valueOf(CafContext.Current.Session.LoginTime.Year);
      befContext.getParams().put(RepositoryConst.Fiscal, yearInfo);
      befContext.setCurrentSessionId(getSessionId());
    }
    return befContext;
  }

  private java.util.Stack<CefScopeNode> privateScopeNodeStack;

  public final java.util.Stack<CefScopeNode> getScopeNodeStack() {
    return privateScopeNodeStack;
  }

  public final void setScopeNodeStack(java.util.Stack<CefScopeNode> value) {
    privateScopeNodeStack = value;
  }

  @Override
  public final java.util.Map<String, ICefSessionItem> getSessionItems() {
    return sessionItemsStack.peek();
  }

  public java.util.List<BEFuncSessionBase> getBESessionItems() {
    return getSessionItems().values().stream().filter(item -> (item instanceof BEFuncSessionBase))
        .map(item -> (BEFuncSessionBase) item).collect(Collectors.toList());
  }

  public final java.util.List<IBEManager> getAllBEManagers() {
    Collection<ICefSessionItem> beSessionItems = getSessionItems().values();
    java.util.ArrayList<IBEManager> rez =
        new java.util.ArrayList<IBEManager>(beSessionItems.size());
    for (ICefSessionItem beSession : beSessionItems) {
      if(!(beSession instanceof BEFuncSessionBase)) {
        continue;
      }
      if (((BEFuncSessionBase)beSession).getBEManager() == null) {
        continue;
      }
      rez.add(((BEFuncSessionBase)beSession).getBEManager());
    }
    return rez;
  }
  // endregion

  // region 方法
  public final BEFuncSessionBase initFuncSessionItem(
      String beType, java.util.function.Function<FuncSession ,BEFuncSessionBase> creator) {
    BEFuncSessionBase existing = (BEFuncSessionBase) getSessionItems().get(beType);
    if (existing == null) {
      existing =creator.apply(this);
      getSessionItems().put(beType, existing);
    }
    return existing;
  }

  public final BEFuncSessionBase getFuncSessionItem(String beType) {
    ICefSessionItem rez = getSessionItems().get(beType);
    return (BEFuncSessionBase) rez;
  }

  // endregion

  //region Transaction
  public Stack<Map<String, ICefSessionItem>> sessionItemsStack = new Stack<>();

  public Stack<Map<String, ICefSessionItem>> getSessionItemsStack() {
    return sessionItemsStack;
  }
// tag: session性能测试
//  /**
//   * added for SessionLog
//   */
//  private String userId;
//  private Instant createTime;
//  private String funcId;
//
//  public String getUserId() {
//    return userId;
//  }
//
//  public List getBeIds() {
//    List list =new ArrayList<>();
//    for(BEFuncSessionBase sessionBase: getBESessionItems()) {
//      list.add(sessionBase.getBEType());
//    }
//    return list;
//  }
//
//  public Instant getCreateTime() {
//    return createTime;
//  }
//
//  public String getFuncId() {
//    return this.funcId;
//  }
//
//  public void setFuncId(String funcId) {
//    this.funcId = funcId;
//  }
//endregion

  //region 超时释放
  @Getter
  @Setter
  private LocalDateTime cafLastOn = LocalDateTime.now();//用于caf在redis中的超时控制, 临时增加解决zj不释放问题, 后期与caf解耦后就不需要了.

  private final Duration persistenceDuration;

  @Getter
  private LocalDateTime lastOn;

  void updateLast(){
    lastOn = LocalDateTime.now();
  }

  public boolean isExpired() {
    if(persistenceDuration == null){
      return false;
    }
    Duration dur = Duration.between(lastOn, LocalDateTime.now());
    return dur.compareTo(persistenceDuration) > 0;
  }
  //endregion

  //region 作废
  @Getter
  private boolean invalid = false;
  private Throwable ce;

  public void invalid(Throwable ce) {
    invalid = true;
    this.ce = ce;
  }

  public void checkValid() {
    if(invalid) {
      throw new CorruptedSessionException((Exception) ce);
    }
  }
  //endregion

  //region tcc
  private Map<String, SoftReference<BefTccParam>> tccParams;
  public void addTccParam(BefTccParam param) {
    Objects.requireNonNull(param);

    if(tccParams == null)
      tccParams = new ConcurrentHashMap<>();

    if(tccParams.putIfAbsent(param.getLogId(), new SoftReference(param)) != null)
      throw new RuntimeException("重复的tccLogId");
  }

  public BefTccParam getTccParam(String logId) {
    if(tccParams == null)
      return null;
    SoftReference<BefTccParam> rezRef = tccParams.remove(logId);
    return rezRef != null ? rezRef.get() : null;
  }

  //endregion
//  @Getter
//  private ArrayList<IBEManager> actionMgrList = new ArrayList<>();

  @Deprecated
  public ArrayList<IBEManager> getActionMgrList() {
    throw new RuntimeException("已过期");
  }

  @Getter(onMethod_={@Override})
  @Setter
  private int version = 0;

  @Getter(onMethod_={@Override})
  @Setter
  private String userSessionId;

  //region editable
  final LinkedList<RootEditToken> tokens = new LinkedList<>();

//  public RootEditToken getCurrentEditToken() {
//    return tokens.poll();
//  }

  SessionEditToken beginEdit() {
    updateLast();
    if(!distributed) {
      return new EmptyEditToken();
    }
    RootEditTokenImpl token = new RootEditTokenImpl(this);
    tokens.push(token);
    if(befContext != null) {
      token.putCallContextOnlyAbsent("befcontextparam", befContext.getParams());
    }
    if(tokens.size() == 1) {
      try {
        token.setItems(new ArrayList<>(getSessionItems().values()));
        for (ICefSessionItem item : getSessionItems().values()) {
          ((DistributedSessionPart) item).beginEdit(token);
        }
      } catch (Exception e) {
        invalid(e);
        throw new CorruptedSessionException(e);
      }
    }
    return token;
  }

  void endEdit(SessionEditToken t) {
    if(!distributed) {
      return;
    }
    RootEditToken token = tokens.pop();
    if(token == null)
      throw new RuntimeException("no token");
    if(t != token)
      throw new RuntimeException("token mismatched");
    try{
      if(!tokens.isEmpty()) {
        return;
      }
      for(ICefSessionItem item : getSessionItems().values()) {
        ((DistributedSessionPart)item).endEdit(token);
      }
    } catch(Exception e) {
      CorruptedSessionException ce = new CorruptedSessionException(e);
      invalid(ce);
      throw ce;
    }
  }

  public void notifySave() {
    for (ICefSessionItem item : getSessionItems().values()) {
      if(item instanceof DistributedSessionPart)
        ((DistributedSessionPart)item).notifySave();
    }
  }

//  @Override
//  public void cancelEdit(SessionEditToken t) {
//    RootEditTokenImpl token = tokens.pop();
//    if(token == null)
//      throw new RuntimeException("no token");
//    try{
//      for(ICefSessionItem item : getSessionItems().values()) {
//        ((IBefTransactionItem)item).rollBack(token);
//      }
//      if(dbTransactionManager != null) {
//        dbTransactionManager.rollBack(token);
//      }
//    } catch(Exception e) {
//        CorruptedSessionException ce = new CorruptedSessionException(e);
//        invalid(ce);
//        throw ce;
//    }
//  }

  private class EmptyEditToken extends RootEditTokenImpl {
    public EmptyEditToken() {
      super(FuncSession.this);
    }
  }

  public SessionItemMap newSessionItemMap() {
    return new SessionItemMap();
  }
  //endregion

  public class SessionItemMap implements Map<String, ICefSessionItem> {

    private LinkedHashMap<String, ICefSessionItem> inner = new LinkedHashMap<>();

    public void innerPut(String key, ICefSessionItem value) {
      inner.put(key, value);
    }
    @Override
    public int size() {
      return inner.size();
    }

    @Override
    public boolean isEmpty() {
      return inner.isEmpty();
    }

    @Override
    public boolean containsKey(Object key) {
      return inner.containsKey(key);
    }

    @Override
    public boolean containsValue(Object value) {
      return inner.containsValue(value);
    }

    public <T extends ICefSessionItem> T itemAt(int index) {
      Iterator<ICefSessionItem> iterator = inner.values().iterator();
      T rez = null;
//      int i = 0;
      for(int i = 0; i<index; ++i ) {
        iterator.next();
      }
      return (T)iterator.next();
//      while(iterator.hasNext() && i++ <= index) {
//        rez = (T) iterator.next();
//      }
//      if(i != index + 1 ) {
//        throw new RuntimeException();
//      }
//      return rez;
    }

    @Override
    public ICefSessionItem get(Object key) {
      return inner.get(key);
    }

    @Override
    public ICefSessionItem put(String key, ICefSessionItem value) {
      if(inner.putIfAbsent(key, value) != null) {
        throw new RuntimeException();
      }
      if(!FuncSession.this.tokens.isEmpty() && value instanceof DistributedSessionPart) {
        ((DistributedSessionPart) value).beginEdit(FuncSession.this.tokens.getLast());
      }
      return null;
    }

    @Override
    public ICefSessionItem remove(Object key) {
      throw new RuntimeException();
    }

    @Override
    public void putAll(Map<? extends String, ? extends ICefSessionItem> m) {
      for (Entry<? extends String, ? extends ICefSessionItem> entry : m.entrySet()) {
        put(entry.getKey(), entry.getValue());
      }
    }

    @Override
    public void clear() {
      throw new RuntimeException();
    }

    @Override
    public Set<String> keySet() {
      return inner.keySet();
    }

    @Override
    public Collection<ICefSessionItem> values() {
      return inner.values();
    }

    @Override
    public Set<Entry<String, ICefSessionItem>> entrySet() {
      return Collections.unmodifiableSet(inner.entrySet());
    }
  }

}
